﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace BarcodePrint
{
    public class PrintWithTCP
    {
        /// <summary>
        /// 远程主机 IP 地址
        /// </summary>
        public String RemoteIPAddress { get; set; }
        /// <summary>
        /// 远程主机端口号
        /// </summary>
        public Int32 RemotePort { get; set; }
        /// <summary>
        /// 本地主机 IP 地址，可不选
        /// </summary>
        public String LocalIPAddress { get; set; }
        /// <summary>
        /// Socket 连接超时，可不选，单位 ms，默认 1000
        /// </summary>
        public Int32 TCPSendReceiveTimeout { get; set; }
        /// <summary>
        /// TcpClient 只读
        /// </summary>
        public TcpClient TCPClient { get; private set; }

        public PrintWithTCP(string remoteIPAddress, int remotePort, int timeout = 1000, string localIPAddress = null)
        {
            this.RemoteIPAddress = remoteIPAddress;
            this.RemotePort = remotePort;
            this.LocalIPAddress = localIPAddress;
            this.TCPSendReceiveTimeout = timeout;

            this.TCPClient = null;
        }

        public bool Print(string cmd)
        {
            byte[] bytes = Encoding.Default.GetBytes(cmd);
            return this.PrintToTCP(bytes);
        }

        public bool Print(byte[] bytes)
        {
            return this.PrintToTCP(bytes);
        }

        private bool TCPConnect()
        {
            try
            {
                this.TCPClient = TimeoutSocket.Connect(this.LocalIPAddress, this.RemoteIPAddress, this.RemotePort, 1000);
                this.TCPClient.SendTimeout = this.TCPSendReceiveTimeout;
                this.TCPClient.ReceiveTimeout = this.TCPSendReceiveTimeout;
                this.TCPClient.Client.Blocking = true;
            }
            catch
            {
                Thread.Sleep(1000);
                return false;
            }
            return this.TCPClient.Connected;
        }

        /// <summary>
        /// 开始一个 Socket 连接
        /// </summary>
        /// <param name="reTryTimes">尝试连接次数</param>
        /// <returns>返回是否连接成功标志</returns>
        public bool BeginConnect(int reTryTimes = 10)
        {
            bool flag = false;
            for (int i = 0; i < reTryTimes; i++)
            {
                if (!flag) { flag = this.TCPConnect(); }
                else { break; }
            }
            if (!flag) { throw new Exception("连接不成功，请检查打印机或网络设置。"); }

            return flag;
        }
        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="bytes">byte 数组</param>
        public void Send(byte[] bytes)
        {
            this.TCPClient.Client.Send(bytes);
        }
        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="cmd">打印命令</param>
        public void Send(string cmd)
        {
            byte[] bytes = Encoding.Default.GetBytes(cmd);
            this.TCPClient.Client.Send(bytes);
        }
        /// <summary>
        /// 接收数据
        /// </summary>
        /// <param name="bytes">byte 数组</param>
        public void Receive(byte[] bytes)
        {
            this.TCPClient.Client.Receive(bytes);
        }
        /// <summary>
        /// 关闭 Socket 连接
        /// </summary>
        public void EndConnect()
        {
            if (this.TCPClient != null)
            {
                if (this.TCPClient.Client != null)
                {
                    this.TCPClient.Client.Shutdown(SocketShutdown.Both);
                    this.TCPClient.Client.Disconnect(false);
                    this.TCPClient.Client.Close();
                    this.TCPClient.Client = null;
                }
                this.TCPClient.Close();
                this.TCPClient = null;
            }
        }

        private bool PrintToTCP(byte[] bytes)
        {
            try
            {
                this.BeginConnect();
                this.Send(bytes);
                return true;
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                this.EndConnect();
            }
        }
    }

    public class TimeoutSocket
    {
        private static bool isConnectionSuccessful = false;
        private static Exception exSocket;
        private static ManualResetEvent timeoutObject = new ManualResetEvent(false);

        public static TcpClient Connect(string localIP, string remoteIP, int remotePort, int timeoutMilliseconds)
        {
            timeoutObject.Reset();
            exSocket = null;
            TcpClient tcpClient = string.IsNullOrEmpty(localIP) ? new TcpClient() : new TcpClient(new IPEndPoint(IPAddress.Parse(localIP), 0));
            tcpClient.BeginConnect(remoteIP, remotePort, new AsyncCallback(CallBackMethod), tcpClient);
            if (timeoutObject.WaitOne(timeoutMilliseconds, false))
            {
                if (isConnectionSuccessful)
                {
                    return tcpClient;
                }
                else
                {
                    if (null != tcpClient)
                    {
                        if (null != tcpClient.Client)
                        {
                            tcpClient.Client.Close();
                            tcpClient.Client = null;
                        }
                        tcpClient.Close();
                        tcpClient = null;
                    }
                    if (null != exSocket)
                    {
                        throw exSocket;
                    }
                    else
                    {
                        throw new TimeoutException("连接远程主机失败。");
                    }
                }
            }
            else
            {
                if (null != tcpClient)
                {
                    if (null != tcpClient.Client)
                    {
                        tcpClient.Client.Close();
                        tcpClient.Client = null;
                    }
                    tcpClient.Close();
                    tcpClient = null;
                }
                throw new TimeoutException("连接远程主机超时。");
            }
        }

        private static void CallBackMethod(IAsyncResult asyncresult)
        {
            try
            {
                isConnectionSuccessful = false;
                if (null != asyncresult)
                {
                    TcpClient tcpClient = asyncresult.AsyncState as TcpClient;
                    if (null != tcpClient && null != tcpClient.Client && tcpClient.Client.Connected)
                    {
                        tcpClient.EndConnect(asyncresult);
                        isConnectionSuccessful = true;
                    }
                }
            }
            catch (Exception ex)
            {
                isConnectionSuccessful = false;
                exSocket = ex;
            }
            finally
            {
                timeoutObject.Set();
            }
        }
    }
}
